//
//  LJAccount.m
//  LJClientKit
//
//  Created by che on 12/7/09.
//  Copyright 2009 AntonChebotaev. All rights reserved.
//

#import "LJAccount.h"

#pragma mark Tech constants
NSString * const LJAccountDefaultServerUrl = @"http://livejournal.com/interface/xmlrpc:80/";

#pragma mark Exceptions
NSString * const LJAccountBadInitException = @"LJAccountBadInitException";

#pragma mark Notifications
NSString * const LJAccountWillUpdateNotification = @"LJAccountWillUdateNotification";
NSString * const LJAccountDidUpdateNotification = @"LJAccountDidUpdateNotification";

NSString* const LJAccountWillUpdateUserpicsNotification = @"LJAccountWillUpdateUserpicsNotification";
NSString* const LJAccountDidUpdateUserpicsNotification = @"LJAccountDidUpdateUserpicsNotification";

NSString* const LJAccountWillUpdateTagsNotification = @"LJAccountWillUpdateTagsNotification";
NSString* const LJAccountDidUpdateTagsNotification = @"LJAccountDidUpdateTagsNotification";

NSString* const LJAccountWillUpdateFriendsNotification = @"LJAccountWillUpdateFriendsNotification";
NSString* const LJAccountDidUpdateFriendsNotification = @"LJAccountDidUpdateFriendsNotification";

@interface LJAccount (Private)
+ (void)addItemToMenu:(NSMenu *)menu withTitle:(NSString *)aTitle object:(NSObject *)anObject image:(NSImage *)anImage;
+ (NSString *)stringFromObject:(id)object;
@end

@implementation LJAccount

#pragma mark -
#pragma mark Sythesizers
@synthesize server;
@synthesize username;
@synthesize friends;
@synthesize friendGroups;
@synthesize userpics;
@synthesize defaultUserpic;
@synthesize defaultJournal;
@synthesize isUpdating = updating;

- (NSArray *)journals {
	NSArray *sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name"
																					  ascending:YES
																					   selector:@selector(caseInsensitiveCompare:)]];
	NSMutableArray *result = [NSMutableArray arrayWithObject:defaultJournal];
	[result addObjectsFromArray:[journals sortedArrayUsingDescriptors:sortDescriptors]];
	return result;
}

#pragma mark Lifecycle
- (id)init {
	[self dealloc];
    @throw [NSException exceptionWithName:LJAccountBadInitException
								   reason:@"Initialize LJAccount with simple init without server url and/or username"
								 userInfo:nil];
    return nil;	
}

- (id)initWithServerURL:(NSURL *)serverURL username:(NSString *)aUsername {
	self = [super init];
	if (self) {
		// Do all necessary notifications
		username = [aUsername retain];
		server = [[LJServer alloc] initWithURL:serverURL];
		
		journals = [[NSMutableSet alloc] init];
		defaultJournal = [[LJJournal alloc] initWithName:aUsername andAccount:self];
		
		friends = [[NSMutableSet alloc] init];
		friendGroups = [[NSMutableSet alloc] init];
		userpics = [[NSMutableSet alloc] init];
		defaultUserpic = nil;
		
		updating = NO;
	}
	return self;
}

- (id)initWithUsername:(NSString *)aUsername {
	return [self initWithServerURL:[NSURL URLWithString:LJAccountDefaultServerUrl] username:aUsername];
}

- (void)dealloc {
	[server release];
	[username release];
	[journals release];
	[friends release];
	[friendGroups release];
	[userpics release];
		
	if (defaultUserpic) {
		[defaultUserpic release];
	}
	
	[super dealloc];
}

#pragma mark NSCoding implementation
- (id)initWithCoder:(NSCoder *)decoder {
	self = [super init];
    if (self) {		
		server 			= [[decoder decodeObjectForKey:@"LJAccountCodingServer"] retain];
		username		= [[decoder decodeObjectForKey:@"LJAccountCodingUsername"] retain];
		
		journals		= [[decoder decodeObjectForKey:@"LJAccountCodingJournals"] retain];
		friends			= [[decoder decodeObjectForKey:@"LJAccountCodingFriends"] retain];
		friendGroups	= [[decoder decodeObjectForKey:@"LJAccountCodingFriendGroups"] retain];
		userpics 		= [[decoder decodeObjectForKey:@"LJAccountCodingUserpics"] retain];
		defaultUserpic 	= [[decoder decodeObjectForKey:@"LJAccountCodingDefaultUserpic"] retain];
		defaultJournal	= [[decoder decodeObjectForKey:@"LJAccountCodingDefaultJournal"] retain];	
		
		updating = NO;
        NSAssert(server != nil, @"LJTag decoded without a server name.");
        NSAssert(username != nil, @"LJTag decoded without a name.");
    }
    return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
	@synchronized(self) {
		[encoder encodeObject:server 		 forKey:@"LJAccountCodingServer"];
		[encoder encodeObject:username 		 forKey:@"LJAccountCodingUsername"];
		[encoder encodeObject:journals 		 forKey:@"LJAccountCodingJournals"];
		[encoder encodeObject:friends	 	 forKey:@"LJAccountCodingFriends"];
		[encoder encodeObject:friendGroups 	 forKey:@"LJAccountCodingFriendGroups"];
		[encoder encodeObject:userpics	 	 forKey:@"LJAccountCodingUserpics"];
		[encoder encodeObject:defaultUserpic forKey:@"LJAccountCodingDefaultUserpic"];
		[encoder encodeObject:defaultJournal forKey:@"LJAccountCodingDefaultJournal"];
	}
}

#pragma mark NSObject overwrites
- (NSString *)description {
	return [NSString stringWithFormat:@"%@ @ %@", username, server];
}

- (BOOL)isEqual:(id)anObject {
	return [server isEqual:[anObject server]] && [username isEqual:[anObject username]];
}

#pragma mark Updaters

- (NSString *)updateFromServer:(NSString *)password {
	DEBUG(@"DEBUG: (updateFromServer:) method started");

	// Shout about account update
	[[NSNotificationCenter defaultCenter] postNotificationName:LJAccountWillUpdateNotification object:self];

	NSString *loginMessage = nil;
	
	// Ask server for update
	@synchronized(self) {
		updating = YES;		
	}	
	NSDictionary* responseDictionary = [server loginForUsername:username password:password];
	
	if (!responseDictionary) {
		[[NSNotificationCenter defaultCenter] postNotificationName:LJAccountDidUpdateNotification object:self];
		@synchronized(self) {
			updating = NO;		
		}			
		return @"Failed to connect to server";
	}
	
	loginMessage = [responseDictionary objectForKey:@"message"];

	@synchronized(self) {
		// Process journals
		NSArray *journalsNames		= [responseDictionary objectForKey:@"usejournals"];
		for(NSString *journalName in journalsNames) {
			if ([journalName isEqual:username]) {
				continue;
			}
			LJJournal *journal = [LJJournal journalWithName:journalName andAccount:self];
			[journals addObject:journal];
		}
			 
		// Process userpics
		NSArray *userpicKeywords	= [responseDictionary objectForKey:@"pickws"];
		NSArray *userpicURLStrings	= [responseDictionary objectForKey:@"pickwurls"];
		for(int index = 0; index < [userpicKeywords count]; ++index) {
			id keywordObject = [userpicKeywords objectAtIndex:index];

			[userpics addObject: [LJUserpic userpicWithKeyword:[NSString utfStringFromObject:keywordObject]
														andURL:[NSURL URLWithString:[userpicURLStrings objectAtIndex:index]]]];
		}
		
		// Add default userpic
		if (defaultUserpic) {
			[defaultUserpic release];
		}
		NSURL *defaultUserpicURL = [NSURL URLWithString:[responseDictionary objectForKey:@"defaultpicurl"]];
		defaultUserpic = [[LJUserpic alloc] initWithKeyword:@"default" andURL:defaultUserpicURL];
		
		// Add groups
		for(NSDictionary *group in [responseDictionary objectForKey:@"friendgroups"]) {
			[friendGroups addObject:[LJFriendsGroup groupWithID:[[group objectForKey:@"id"] intValue]
														   name: [group objectForKey:@"name"]
												      sortOrder:[[group objectForKey:@"sortorder"] intValue]
												     publicMode:[[group objectForKey:@"public"] intValue]   ]];			
		}
		
		updating = NO;
	} // end of @synchronized(self)
	
	NSDictionary *info = [NSDictionary dictionaryWithObject:loginMessage forKey:@"LJAccountLoginMessage"];
	[[NSNotificationCenter defaultCenter] postNotificationName:LJAccountDidUpdateNotification object:self userInfo:info];
	DEBUG(@"DEBUG: (updateFromServer:) method finished");
	return loginMessage;
}

- (void)updateFriends:(NSString *)password {
	DEBUG(@"DEBUG: (updateFriends:) method started");

	// Shout about account update
	[[NSNotificationCenter defaultCenter] postNotificationName:LJAccountWillUpdateFriendsNotification object:self];
	
	// Ask server for update
	@synchronized(self) {
		updating = YES;		
	}	
	NSDictionary* responseDictionary = [server friendsForUsername:username password:password];
	
	if (!responseDictionary) {
		[[NSNotificationCenter defaultCenter] postNotificationName:LJAccountDidUpdateFriendsNotification object:self];
		@synchronized(self) {
			updating = NO;		
		}			
		return;
	}

	@synchronized(self) {
		// Process journals
		NSArray *friendsArray = [responseDictionary objectForKey:@"friends"];
		for(NSDictionary *friendKeys in friendsArray) {
			NSString *typeString = [friendKeys objectForKey:@"type"];
			LJFriendType type;
			if (!typeString) {
				type = LJPerson;
			} else if ([typeString isCaseInsensitiveLike:@"community"]) {
				type = LJCommunity;
			} else if ([typeString isCaseInsensitiveLike:@"syndicated"]) {
				type = LJSyndicated;
			} else {
				type = LJOtherFriendType;
			}

			// We use description here to be sure that username and Full Name are strings
			// (if username is, for example, 239 it will be NSCFNumber typed)
			LJFriend *friend = [LJFriend friendWithUsername:[[friendKeys objectForKey:@"username"] description]
												   fullName:[[friendKeys objectForKey:@"fullname"] description]
													   type:type];
			[friends addObject:friend];
		}	
				
		updating = NO;
	} // end of @synchronized(self)
	
	[[NSNotificationCenter defaultCenter] postNotificationName:LJAccountDidUpdateFriendsNotification object:self];	
	DEBUG(@"DEBUG: (updateFriends:) method finished");
}

- (void)updateTags:(NSString *)password {
	DEBUG(@"DEBUG: (updateTags:) method started");

	// Shout about account update
	[[NSNotificationCenter defaultCenter] postNotificationName:LJAccountWillUpdateTagsNotification object:self];
	
	// Ask server for update
	@synchronized(self) {
		updating = YES;		
	}	
	
	NSMutableArray *allJournals = [NSMutableArray arrayWithObject:defaultJournal];
	@synchronized(self) {
		[allJournals addObjectsFromArray:[journals allObjects]];
	}
	for(LJJournal *journal in allJournals) {
		TRACE(@"DEUBG: Loading tags for journal %@", journal);
		NSDictionary* responseDictionary = [server tagsForUsername:username
														   journal:[journal name]
														  password:password];
		
		if (!responseDictionary) {
			continue;
		}
		
		@synchronized(self) {
			// Process tags
			NSArray *tagsArray = [responseDictionary objectForKey:@"tags"];
			for(NSDictionary *tagKeys in tagsArray) {
				LJTag *tag = [LJTag tagWithName:[[tagKeys objectForKey:@"name"] description]
										   uses:[[tagKeys objectForKey:@"uses"] intValue]];
				[[journal tags] addObject:tag];
			}	
		TRACE(@"DEUBG: Tags loaded for journal %@", journal);	
		} // end of @synchronized(self)
	}
	
	@synchronized(self) {
		updating = NO;		
	}	
	
	[[NSNotificationCenter defaultCenter] postNotificationName:LJAccountDidUpdateTagsNotification object:self];	
	DEBUG(@"DEBUG: (updateTags:) method finished");
}

- (void)reloadUserpicsImages {
	DEBUG(@"DEBUG: (updateUserpicsImages) method started");

	[[NSNotificationCenter defaultCenter] postNotificationName:LJAccountWillUpdateUserpicsNotification object:self];
	@synchronized(self) {
		updating = YES;		
	}			
	@synchronized(userpics) {
		[defaultUserpic reloadImage];
		[userpics makeObjectsPerformSelector:@selector(reloadImage)];
	}	
	@synchronized(self) {
		updating = NO;		
	}			
	[[NSNotificationCenter defaultCenter] postNotificationName:LJAccountDidUpdateUserpicsNotification object:self];	
	
	DEBUG(@"DEBUG: (updateUserpicsImages) method finished");
}

#pragma mark Updaters Operations
- (NSOperation*)updateFromServerOperation:(NSString*) password {
	NSInvocationOperation* task = [[[NSInvocationOperation alloc] initWithTarget:self
																	    selector:@selector(updateFromServer:)
																		  object:password] autorelease];
	return task;
}
- (NSOperation*)updateFriendsOperation:(NSString*) password {
	NSInvocationOperation* task = [[[NSInvocationOperation alloc] initWithTarget:self
																	    selector:@selector(updateFriends:)
																		  object:password] autorelease];
	return task;
}
- (NSOperation*)updateTagsOperation:(NSString*) password {
	NSInvocationOperation* task = [[[NSInvocationOperation alloc] initWithTarget:self
																	    selector:@selector(updateTags:)
																		  object:password] autorelease];
	return task;
}
- (NSOperation*)reloadUserpicsImagesOperation {
	NSInvocationOperation* task = [[[NSInvocationOperation alloc] initWithTarget:self
																	    selector:@selector(reloadUserpicsImages)
																		  object:nil] autorelease];
	return task;
}

#pragma mark Menus

- (NSMenu *)journalsMenuWithImages:(NSDictionary *)images {
	NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Post To"];
	
	@synchronized(self) {
		[LJAccount addItemToMenu:menu
					   withTitle:[defaultJournal name]
						  object:defaultJournal
						   image:[images objectForKey:@"user"]];
		[menu addItem:[NSMenuItem separatorItem]];
		
		for(LJJournal *journal in journals) {
			[LJAccount addItemToMenu:menu
						   withTitle:[journal name]
							  object:journal
							   image:[images objectForKey:@"community"]];
		}
	}
	[menu setAutoenablesItems:NO];
	return [menu autorelease];
}

+ (NSMenu*)defaultSecurityMenuWithImages:(NSDictionary*)images {
	NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Security"];

	[self addItemToMenu:menu
			  withTitle:@"Public"
				 object:[NSNumber numberWithInt:LJPublicEntry]
				  image:nil];
	
	[self addItemToMenu:menu
			  withTitle:@"Friends Only"
				 object:[NSNumber numberWithInt:LJFriendsOnlyEntry]
				  image:[images objectForKey:@"friends"]];
	
	[self addItemToMenu:menu
			  withTitle:@"Private"
				 object:[NSNumber numberWithInt:LJPrivateEntry]
				  image:[images objectForKey:@"private"]];

	[menu setAutoenablesItems:NO];
	return [menu autorelease];
}

- (NSMenu *)securityMenuWithImages:(NSDictionary *)images {
	NSMenu *menu = [LJAccount defaultSecurityMenuWithImages:images];
	
	@synchronized(self) {
		if ([friendGroups count] > 0) {
			[LJAccount addItemToMenu:menu
						   withTitle:@"Custom"
							  object:[NSNumber numberWithInt:LJGroupAllowedEntry]
							   image:[images objectForKey:@"custom"]];
			[[menu itemWithTitle:@"Custom"] setHidden:YES];
			[menu addItem:[NSMenuItem separatorItem]];
			
			for(LJFriendsGroup* group in friendGroups) {
				[LJAccount addItemToMenu:menu
							   withTitle:[group name]
								  object:group
								   image:[images objectForKey:@"custom"]];
			}
		}
	}

	return menu;
}
- (NSMenu *)userpicsMenu {
	NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Userpics"];
	
	@synchronized(self) {
		[LJAccount addItemToMenu:menu
					   withTitle:@"default"
						  object:defaultUserpic
						   image:nil];			
		[menu addItem:[NSMenuItem separatorItem]];
	
		
		for(LJUserpic *userpic in userpics) {
			[LJAccount addItemToMenu:menu
						   withTitle:[userpic keyword]
							  object:userpic
							   image:nil];			
		}		
	}
	return [menu autorelease];
}
// helper for previous menu methods
+ (void)addItemToMenu:(NSMenu *)menu withTitle:(NSString *)aTitle object:(NSObject *)anObject image:(NSImage *)anImage {
	NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:aTitle
												   action:nil
											keyEquivalent:@""] autorelease];
	[item setImage:anImage];
	[item setRepresentedObject:anObject];
	[menu addItem:item];
}

@end
